package org.xmlbinder;

import static org.xmlpull.v1.XmlPullParser.*;

import java.io.InputStream;
import java.io.Reader;
import java.util.*;
import java.util.logging.Logger;

import org.xmlpull.v1.*;

/**
 * Bind an XML data to an Object tree. Use accomapanying annotations to map the elements .
 *
 * @author Noble Paul (noble.paul@gmail.com)
 */
public class XmlBinder {
    private static HashMap<Class, BeanInjector> injectorCache = new HashMap<Class, BeanInjector>();
    private Logger logger ;
    private boolean ignoreWarnings = true;

    //public methods
    /** Binds a stream of xml to the passed object as the root object.
     * @param is The imputstream from which to read the xml
     * @param result The root object
     * @return The result Object that was passed
     */
    public <T> T bind(InputStream is, T result) {
        return bind(getPullParser(is, null), result);
    }

    /**Bind a stream of xml to an Object of the given class. It creates a new instance
     * @param is The imputstream from which to read the xml
     * @param clz The class of the root object
     * @return The object tree with the data
     */
    public <T> T bind(InputStream is, Class<T> clz) {
        return bind(getPullParser(is, null), createInst(clz));
    }

    /**Bind a character stream of xml to an Object of the given class. It creates a new instance
     * @param reader The character imputstream from which to read the xml
     * @param clz The class of the root object
     * @return The object tree with the data
     */
    public <T> T bind(Reader reader, Class<T> clz) {
        return bind(getPullParser(null, reader), createInst(clz));
    }

    /** Binds a character stream of xml to the passed object as the root object.
     * @param reader The character imputstream from which to read the xml
     * @param result The root object
     * @return The result Object that was passed
     */
    public <T> T bind(Reader reader, T result) {
        return bind(getPullParser(null, reader), result);
    }




    public boolean isIgnoreWarnings() {
        return ignoreWarnings;
    }
    
    public XmlBinder setIgnoreWarnings(boolean ignoreWarnings) {
        this.ignoreWarnings = ignoreWarnings;
        return this;
    }

    //public methods end


    private <T> T bind(XmlPullParser xpp, T result) {
        while (true) {
            try {
                int event = xpp.next();
                if (event == START_TAG) {
                    getBeanInjectorFromCache(result.getClass()).
                            inject(xpp, result, null, this);
                }
                if (event == END_DOCUMENT) break;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return result;
    }


    private <T> T createInst(Class<T> clz) {
        T result = null;
        try {
            result = clz.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }


    static BeanInjector getBeanInjectorFromCache(Class clz) {
        BeanInjector beanInjector = injectorCache.get(clz);
        if (beanInjector == null) {
            beanInjector = new BeanAnnotationProcessor(clz);
            injectorCache.put(clz, beanInjector);
        }
        return beanInjector;
    }

    static XmlPullParser getPullParser(InputStream is, Reader rdr) {
        try {
            XmlPullParser xpp = factory.newPullParser() ;
            if (rdr != null) {
                xpp.setInput(rdr);
            } else {
                xpp.setInput(is, "utf-8");
            }
            return xpp;
        } catch (XmlPullParserException e) {
            throw new RuntimeException(e);
        }
    }

    static XmlPullParserFactory factory;

    static {
        checkFactory();
    }

    private static void checkFactory() {
        XmlPullParserFactory factory = null;
        try {
            factory = XmlPullParserFactory.newInstance();
            factory.setNamespaceAware(true);
        } catch (XmlPullParserException e) {
            throw new RuntimeException(e);
        }


    }
}
